package com.sitech.ibnms.c3p.command.script;

import com.sitech.ibnms.c3p.command.CommandParamsService;
import com.sitech.ibnms.c3p.command.CommandSupport;
import com.sitech.ibnms.c3p.domain.C3pTaskResult;
import com.sitech.ibnms.c3p.domain.ResourceAccount;
import com.sitech.ibnms.c3p.mappers.*;
import com.sitech.ibnms.c3p.task.C3pServerLogTaskService;
import com.sitech.ibnms.c3p.util.C3pConstants;
import com.sitech.ibnms.c3p.util.JSONUtil;
import com.sitech.ismp.messageObject.cc.CccpConstants;
import com.sitech.ismp.messageObject.cc.CccpResults;
import com.sitech.ismp.workflow.dao.ProcessInstanceDAO;
import com.sitech.ismp.workflow.domain.ProcessInstance;
import com.sitech.ismp.workflow.domain.Task;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import request.ScriptRequest;

import java.util.*;
import java.util.Map.Entry;

import static com.sitech.ismp.messageObject.cc.CccpConstants.EXECUTE_FAILURE;
import static org.apache.commons.lang3.time.DateFormatUtils.format;

/**
 * Created by Administrator on 2015/1/12.
 */
public abstract class ScriptCommand extends CommandSupport {

    @Autowired
    private C3pScriptMapper scriptMapper;
    @Autowired
    private C3pTaskNodeExtMapper nodeExtMapper;
    @Autowired
    private CommandParamsService paramsService;
    @Autowired
    private ProcessInstanceDAO processInstanceDAO;
    @Autowired
    private C3pServerLogTaskService logTaskService;
    @Autowired
    private ResourceAccountMapper resourceAccountMapper;
    @Autowired
    private ScriptResultMessageListener messageListener;
    @Autowired
    private C3pTaskResultMapper taskResultMapper;
    @Autowired
    private C3pTaskMapper c3pTaskMapper;
    protected Logger scriptLogger = LoggerFactory.getLogger("c3p.task.command.script.content");
    protected Logger scriptOutLogger = LoggerFactory.getLogger("c3p.task.command.script.results");

    /**
     * @return 目标执行环境类型名称
     */
    private String getTargetName() {
        switch (this.getType()) {
            case SHELL:
                return "执行主机";
            case SQL:
                return "执行数据库";
            default:
                return "执行环境";
        }
    }

    @Override
    public void doExecute(String c3pTaskId, Task task) {
        int serialNo = task.getSerialNo();
        int currActId = task.getCurrActId();
        int taskId = task.getTaskId();
        logger.info("Executing task[serialNo={}, activityId={}, taskId={}]", serialNo, currActId, taskId);
        logger.info("Executing task[taskJson={}]", JSONUtil.toJSON(task));
        ProcessInstance processInstance = processInstanceDAO.queryProcessInstanceInfoBySerialNo(serialNo);
        String scheduleId = processInstance == null ? getClass().getName() : processInstance.getWf_num();

        List<String> resourceIdList = nodeExtMapper.queryResourceId(task.getProcessId(), currActId);
        // 设置resourceId
        logger.debug("resourceId:{}", resourceIdList);

        String targetName = getTargetName();
        Assert.notEmpty(resourceIdList, "未配置对应的" + targetName + "!");
        List<ResourceAccount> resourceAccounts = resourceAccountMapper.findByIdList(resourceIdList);
        logger.info("****************resourceId = "+resourceIdList);
        System.out.println("****************resourceId = "+resourceIdList);
        Assert.notEmpty(resourceAccounts, "未根据配置的resourceIds 找到对应的" + targetName + ", resourceId=" + JSONUtil.toJSON(resourceIdList));

        String scriptContent = StringUtils.join(scriptMapper.findScriptContentById(this.getCommandId()), '\n');
        final Integer displayMode=scriptMapper.findScriptDisplayModeById(this.getCommandId());
        logger.info("displayMode:{}", displayMode);
        if(displayMode!=null&&resourceAccounts!=null){
            for(ResourceAccount resourceAccount:resourceAccounts){
                resourceAccount.setDisplayMode(displayMode);
            }
        }
        Map<String, String> outVars = paramsService.prepareParamsOutConf(task);
        logger.debug("OUT-PARAM:{}", outVars);

        // 保存关于同一环节多执行主机的控制参数
        saveControlParams(c3pTaskId, task, resourceAccounts);

        final String taskType=c3pTaskMapper.queryTaskConfTypeByTaskId(c3pTaskId);
        Set<Map.Entry<String, String>> allParamsIn = new LinkedHashSet<Map.Entry<String, String>>();
        for (int size = resourceAccounts.size(), i = 0; i < size; i++) {
            //运行
            //一个节点  一个脚本  多个 主机  关系表
            String requestId = String.format("task-%s-%s-%s-%s", task.getSerialNo(), task.getCurrActId(), task.getTaskId(), i);
            try {
                final ResourceAccount resourceAccount = resourceAccounts.get(i);
                Map<String, String> paramsIn = paramsService.prepareParamsIn(task, resourceAccount);
                allParamsIn.addAll(paramsIn.entrySet());
                final ScriptRequest request = getScriptRequest(requestId, scheduleId, resourceAccount, scriptContent, paramsIn, outVars);
                final C3pTaskResult taskResult=new C3pTaskResult(c3pTaskId,scheduleId,taskId+"",task.getProcessId(),currActId+"",task.getSerialNo(),resourceAccount.getUnitId());
                this.doExecuteScript(request, resourceAccount).addCallback(new ListenableFutureCallback<CccpResults>() {
                    @Override
                    public void onFailure(Throwable ex) {
                        logger.error("指令执行失败", ex);
                    }

                    @Override
                    public void onSuccess(CccpResults result) {
                        try {
                            messageListener.processResult(result,resourceAccount,taskResult,taskType);
                            saveRunResult(result, taskResult);
                        } catch (Throwable ex) {
                            this.onFailure(ex);
                        }
                    }
                });
            } catch (Exception e) {
                logTaskService.writeTaskNodeLog(task, "指令执行失败:" + e.getMessage());
                logger.error("指令执行失败(" + requestId + ")", e);
            }
        }
        escapePassword(allParamsIn);
        logger.debug("IN-PARAM:{}", allParamsIn);
        logTaskService.writeTaskNodeLog(task, "环节【${nodeName}】脚本输入参数：" + allParamsIn);
        paramsService.saveParamsIn(c3pTaskId, task, allParamsIn); // 将实际的输入参数入库
    }

    protected abstract ListenableFuture<CccpResults> doExecuteScript(ScriptRequest req, ResourceAccount resourceAccount);

    /**
     * 将输入参数中的密码替换成星号，以免将明文密码输出在日志中
     *
     * @param allParamsIn
     * @return
     */
    protected void escapePassword(Set<Entry<String, String>> allParamsIn) {
        for (Entry<String, String> entry : allParamsIn) {
            String key = entry.getKey().toLowerCase();
            // 将输入参数中的密码替换成星号，以免将明文密码输出在日志中
            if (key.contains("password") || key.contains("passwd")) {
                entry.setValue(entry.getValue().replaceAll("[^\\*]", "*"));
            }
        }
    }

    protected ScriptRequest getScriptRequest(String requestId, String scheduleId, ResourceAccount resourceAccount, String scriptContent, Map<String, String> paramsIn, Map<String, String> outVars) {
        ScriptRequest request = new ScriptRequest();
        request.setScheduleId(scheduleId);
        request.setRequestId(requestId);
        request.setContent(scriptContent);
        request.setInputVars(JSONUtil.toJSON(paramsIn));
        request.setScriptType(getType().getScriptType());

        request.setResourceId(resourceAccount.getResId());
        request.setOutputVars(JSONUtil.toJSON(outVars));
        return request;
    }

    protected void saveControlParams(String c3pTaskId, Task task, List<ResourceAccount> resourceAccounts) {
        HashMap<String, String> controlParamsIn = new HashMap<String, String>();
        HashMap<String, String> controlParamsOut = new HashMap<String, String>();
        // 记录该环节总共需要在多少主机执行
        controlParamsIn.put(C3pConstants.PARAM_RESOURCE_COUNT, resourceAccounts.size() + "");
        // 记录该环节已经在多少主机上执行，在收到脚本执行结果后更新此数据
        controlParamsOut.put(C3pConstants.PARAM_RESOURCE_COUNT_EXECUTED, "0");
        paramsService.saveParams(c3pTaskId, task, controlParamsIn.entrySet(), controlParamsOut.entrySet());
    }

    protected CccpResults createResultsInstance(final ScriptRequest request, final ResourceAccount resourceAccount) {
        CccpResults cccpResults = new CccpResults();
        Map<String, String> context = prepareContext(getC3pTaskId(), getTask(), resourceAccount.getResId());
        cccpResults.setParams(new HashMap<String, String>(context));
        cccpResults.getParams().put(C3pConstants.CONTEXT_SCHEDULE_ID, StringUtils.defaultIfEmpty(request.getScheduleId(), CccpConstants.TRIGGER_MANUAL));
        cccpResults.getParams().put(C3pConstants.PARAM_EXECUTE_RESOURCE_ID, request.getResourceId());
        cccpResults.setKpis(new ArrayList<HashMap<String, String>>());
        cccpResults.setVariables(new LinkedHashMap<String, String>());
        return cccpResults;
    }

    protected void failed(CccpResults results, Throwable e) {
        logger.warn("脚本执行失败: {}", results.getParams().get(C3pConstants.PARAM_WORKFLOW_TASK), e);
        results.setEndTime(format(new Date(), DATE_TIME_FORMAT));
        results.setExecFlag(EXECUTE_FAILURE);
        String causeMessage = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
        results.setExecResult(causeMessage);
        results.setFailedReason(causeMessage);
    }
    public void saveRunResult(CccpResults result,C3pTaskResult taskResult){
        logger.info("执行结果报告按行存储--bgein");
        int rowNo=1;
        Map<String,String> params=result.getVariables();

        List<C3pTaskResult> results=new ArrayList<C3pTaskResult>();
        results.add(new C3pTaskResult(taskResult,rowNo++,result.getParams().get(C3pConstants.PARAM_COMMAND_TYPE),"SCRIPT_TYPE"));

        String scriptContent=params.get(C3pConstants.SCRIPT_CONTENT);
        scriptContent=scriptContent==null?"":scriptContent;
        String[] scriptContents=scriptContent.split("\n");
        for(String content:scriptContents){
            results.add(new C3pTaskResult(taskResult,rowNo++,content,"SCRIPT_CONTENT"));
        }

        results.add(new C3pTaskResult(taskResult,rowNo++,result.getExecFlag()!=-1?"SUCCESS":"FAIL","RUN_RESULT_STATUS"));

        String resultStr=result.getExecFlag()!=-1?result.getExecResult():result.getFailedReason();
        resultStr=resultStr==null?"":resultStr;
        String[] resultStrs=resultStr.split("\n");
        for(String str:resultStrs){
            results.add(new C3pTaskResult(taskResult,rowNo++,str,"RUN_RESULT"));
        }
        taskResultMapper.insert(results);
        logger.info("执行结果报告按行存储--end");
    }
}
